//
// Package utils gorm.go Gorm操作相关函数
//
package utils

import (
	"database/sql"
	"errors"
	"fmt"
	"gorm.io/driver/mysql"
	"strconv"
	"strings"
	"time"

	//"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"woo-server/coms/consts"
)

// GORM 全局数据对象
var GORM *gorm.DB

// GormHandler 数据库事务处理函数
type GormHandler func(tx *gorm.DB) (interface{}, error)

// InitGorm 初始化数据库
func InitGorm(dialect string, source string) (err error) {
	dialect = strings.TrimSpace(dialect)
	source = strings.TrimSpace(source)
	if len(dialect) == 0 {
		return errors.New("dialect is empty")
	}
	if len(source) == 0 {
		return errors.New("source is empty")
	}
	GORM, err = gorm.Open(mysql.Open(source), &gorm.Config{})
	if err != nil {
		return fmt.Errorf("can't open db with dialect=[%s] source=[%s] error=[%s]", dialect, source, err)
	}
	sqlDB, _ := GORM.DB()
	sqlDB.SetMaxIdleConns(10)
	return GORM.Error
}

// CleanupGorm 清理数据库
func CleanupGorm() error {
	sqlDB, err := GORM.DB()
	if err != nil {
		return err
	}
	err = sqlDB.Close()
	GORM = nil
	return err
}

// RunGormTran 执行数据库事务
func RunGormTran(handler GormHandler) (ret interface{}, err error) {
	if handler == nil {
		return nil, errors.New("db handler can't not be nil")
	}

	// 开启事务
	tx := GORM.Begin()
	if err := tx.Error; err != nil {
		return nil, err
	}

	// 引入这个方法的目的是handler出现panic（比如除零，数组越界，但依然捕获不到栈溢出）的时候，能够手动回滚事务，而无需等待数据库事务超时，但是defer性能损耗在100ns，还要查询下文档
	defer func() {
		if r := recover(); r != nil {
			// 如果handler发生panic错误时执行
			tx.Rollback()
			// 封装错误
			switch x := r.(type) {
			case string:
				err = errors.New(x)
			case error:
				err = x
			default:
				err = errors.New("Unknown panic")
			}
		}
	}()
	if ret, err = handler(tx); err != nil {
		tx.Rollback()
		return nil, err
	}
	tx.Commit()
	return ret, nil
}

// MapScan 从sql.Rows映射到map
func MapScan(r *sql.Rows, dest map[string]interface{}) error {
	// 参考 github.com/jmoiron/sqlx
	columns, err := r.Columns()
	if err != nil {
		return err
	}
	values := make([]interface{}, len(columns))
	for i := range values {
		values[i] = new(interface{})
	}
	err = r.Scan(values...)
	if err != nil {
		return err
	}
	for i, column := range columns {
		value := *(values[i].(*interface{}))
		switch v := value.(type) {
		case []byte:
			// 坑: json序列化[]byte类型时会生成base64格式的字符串
			// 解决方案: 把[]byte 转换为 string 后再进行json序列化
			value = string(v)
		case time.Time:
			// 格式化时间
			value = v.Format(consts.TimeFormat)
		case nil:
			// 数据库null，返回空字符串
			value = ""
		case int64:
			value = strconv.FormatInt(v, 10)
		default:
			// 后续有其他类型需要特殊处理，可以直接添加case处理
		}
		dest[column] = value
	}
	return r.Err()
}

// Query     通用查询方法
// sql	    查询语句
// params	参数
// return    数据集合，错误
// example   Query("SELECT * FROM tb WHERE id = ?",2)
func Query(sql string, params ...interface{}) ([]map[string]interface{}, error) {
	rows, err := GORM.Raw(sql, params...).Rows()
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	list := []map[string]interface{}{}

	for rows.Next() {
		dest := make(map[string]interface{})
		if scanErr := MapScan(rows, dest); scanErr != nil {
			//  LogError(err.Error())
			return nil, scanErr
		}
		list = append(list, dest)
	}

	return list, nil
}

// QueryForPaging  通用分页查询方法
// pageIndex	      页码
// pageSize	      记录数
// sql	          查询语句
// params	      参数
// return          数据集合，总记录数，错误
// example         QueryForPaging(1,10,"SELECT * FROM oa_bc.base_user where CompanyId = ?",2)
// remark          使用limit进行分页时，页码越大，性能越差；大数据量时的分页情况建议自己写分页语句；
func QueryForPaging(pageIndex int64, pageSize int64, sql string, params ...interface{}) (
	[]map[string]interface{}, int64, error) {
	// 查询分页数据
	skip := (pageIndex - 1) * pageSize
	// sqlPaging := fmt.Sprintf("SELECT * FROM (%s) TMP LIMIT %d,%d", sql, skip, pageSize)
	sqlPaging := fmt.Sprintf("%s LIMIT %d,%d", sql, skip, pageSize)
	list, err := Query(sqlPaging, params...)
	if err != nil {
		return nil, 0, err
	}

	// 查询数据条数
	sqlCount := fmt.Sprintf("SELECT COUNT(1) c FROM (%s) TMP", sql)
	countMap, err := Query(sqlCount, params...)
	if err != nil {
		return nil, 0, err
	}

	count, err := strconv.ParseInt(countMap[0]["c"].(string), 10, 64)
	if err != nil {
		return nil, 0, err
	}

	return list, count, nil
}
